package core

import (
	cf "BackupDatabase/config"
	"fmt"
	"go.uber.org/zap"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type BackCore struct {
	mQConfig       cf.MQConfig
	BackConfig     cf.Back
	Tables         []string
	Group          *sync.WaitGroup
	itemDir        string
	LimitGoroutine chan int
	cmdName        string
	param          string
}

func NewBackCore(MConfig *cf.MConfig, tableFileName string) (*BackCore, error) {
	tables, err := ParsingTableName(tableFileName)
	if err != nil {
		zap.L().Error("获取需要备份的表失败", zap.Error(err))
		return nil, err
	}

	if len(tables) == 0 {
		zap.L().Error("备份表为空")
		return nil, err
	}

	name, param := CmdType()
	group := &sync.WaitGroup{}
	group.Add(len(tables))
	return &BackCore{
		mQConfig:       MConfig.MySqlConfig,
		BackConfig:     MConfig.Back,
		Tables:         tables,
		Group:          group,
		LimitGoroutine: make(chan int, MConfig.Back.MaxGoroutineCount),
		cmdName:        name,
		param:          param,
	}, nil
}

func (b *BackCore) removeFiles() {
	err := os.RemoveAll(b.itemDir)
	if err != nil {
		zap.L().Error("删除临时文件失败", zap.Error(err))
	}
	return
}

func (b *BackCore) backup(tableName string) {
	tableName = strings.TrimSpace(tableName)
	if tableName == "" {
		<-b.LimitGoroutine
		b.Group.Done()
		runtime.Goexit()
	}

	zap.L().Info("正在执行备份", zap.String("name", tableName))
	cmd := fmt.Sprintf("mysqldump -u%s -p%s -h%s -P%s %s %s >%s/%s.sql",
		b.mQConfig.Username,
		b.mQConfig.Password,
		b.mQConfig.Host,
		b.mQConfig.Port,
		b.mQConfig.Db,
		tableName,
		b.itemDir,
		tableName,
	)
	command := exec.Command(b.cmdName, b.param, cmd)

	err := command.Run()
	if err != nil {
		b.Group.Done()
		<-b.LimitGoroutine
		zap.L().Error("执行备份命令失败", zap.String("name", tableName), zap.Error(err))
		runtime.Goexit()
	}
	zap.L().Info("数据表备份成功", zap.String("name", tableName))
	<-b.LimitGoroutine
	b.Group.Done()
}

func (b *BackCore) Run() {
	itemDir, err := CreateDir(b.BackConfig.BackDir)
	if err != nil {
		zap.L().Fatal("临时目录创建失败", zap.Error(err))
		return
	}

	b.itemDir = itemDir

	if len(b.Tables) == 1 {
		if b.Tables[0] == "*" {
			zap.L().Info("正在执行备份", zap.String("name", b.mQConfig.Db))
			cmd := fmt.Sprintf("mysqldump -u%s -p%s -h%s -P%s %s >%s/%s.sql",
				b.mQConfig.Username,
				b.mQConfig.Password,
				b.mQConfig.Host,
				b.mQConfig.Port,
				b.mQConfig.Db,
				b.itemDir,
				b.mQConfig.Db,
			)
			command := exec.Command(b.cmdName, b.param, cmd)
			err := command.Run()
			if err != nil {
				zap.L().Error("执行备份命令失败", zap.String("name", b.mQConfig.Db), zap.Error(err))
				return
			}
			zap.L().Info("数据表备份成功", zap.String("name", b.mQConfig.Db))
		}
	} else {
		for _, table := range b.Tables {
			b.LimitGoroutine <- 0
			go b.backup(table)
		}
		b.Group.Wait()
		err := b.Reset()
		if err != nil {
			zap.L().Fatal("程序重置失败...", zap.Error(err))
		}
	}

	zap.L().Info("正在打包备份包")
	b.dataPackage()
	zap.L().Info("正在删除备份临时文件")
	b.removeFiles()
	zap.L().Info("正在删除多余备份包")
	b.deleteDateFiles()

}

// Reset 重置程序
func (b *BackCore) Reset() error {
	group := &sync.WaitGroup{}
	group.Add(len(b.Tables))
	b.Group = group
	b.LimitGoroutine = make(chan int, cf.Config.Back.MaxGoroutineCount)
	return nil
}

// DataPackage 打包++
func (b *BackCore) dataPackage() {
	date := time.Now()
	dataDir := b.BackConfig.BackDir + "/" + b.BackConfig.ZapDir + "/"
	exists, err := PathExists(dataDir)
	if err != nil {
		zap.L().Error("数据目录判断是否存在错误", zap.Error(err))
		return
	}

	if !exists {
		err = os.MkdirAll(dataDir, 0666)
		if err != nil {
			zap.L().Error("数据目录创建失败", zap.Error(err))
			return
		}
	}
	dataDir = b.zipDir(dataDir, date)
	// 备份完毕打包
	err = Gunzip(b.itemDir, dataDir, b.zipPassword(date))
	if err != nil {
		zap.L().Error("保存zip包失败", zap.Error(err))
		return
	}
	return
}

// deleteDateFiles 删除多余备份文件
func (b *BackCore) deleteDateFiles() {
	DealWithFiles(b.BackConfig.BackDir+"/"+b.BackConfig.ZapDir+"/", b.BackConfig.Count)
}

// zipPassword 获取密码
func (b *BackCore) zipPassword(date time.Time) string {
	if b.BackConfig.Password == "" {
		return ""
	}
	return b.BackConfig.Password + strconv.Itoa(date.Year()) + date.Format("01") + strconv.Itoa(date.Day())
}

// zipDir 获取压缩包路径
func (b *BackCore) zipDir(dataDir string, date time.Time) string {
	dataDir += strconv.Itoa(date.Year()) + date.Format("01") + strconv.Itoa(date.Day()) + "-" + strconv.Itoa(int(date.Unix())) + ".zip"
	return dataDir
}
